﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;

namespace BlockingIO
{
    /// <summary>
    /// 处理意外粘包
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="cmd"></param>
    /// <param name="unexpectedBuffer"></param>
    public delegate void UnexpectedBufferReceived(BIO sender, byte[] cmd, byte[] unexpectedBuffer);

    /// <summary>
    /// 处理返回消息的头
    /// </summary>
    /// <typeparam name="T">交互类型 Byte[] 或 String</typeparam>
    /// <param name="header">消息头部分</param>
    /// <returns>剩余需要获取的字节数</returns>
    public delegate long HeaderReceived<T>(T header);

    public class BIO
    {
        /// <summary>
        /// 尝试重连
        /// </summary>
        /// <returns></returns>
        protected delegate bool TryConnect();

        /// <summary>
        /// 流
        /// </summary>
        protected Stream mStream;

        /// <summary>
        /// 意外粘包
        /// </summary>
        public event UnexpectedBufferReceived UnexpectedBufferReceivedEventHandler;

        /// <summary>
        /// 字符串编码
        /// </summary>
        public Encoding Encoding { get; set; } = Encoding.ASCII;

        /// <summary>
        /// 尝试限制
        /// </summary>
        public int TryLimit { get; set; } = 300;

        /// <summary>
        /// 尝试间隔ms
        /// </summary>
        public int TryDelay { get; set; } = 10;

        protected TryConnect FunctionTryConnect;

        /// <summary>
        /// 写入
        /// </summary>
        /// <param name="cmd">指令</param>
        public void Write(byte[] cmd)
        {
            lock (this)
            {
                if (FunctionTryConnect == null || FunctionTryConnect.Invoke())
                    mStream.Write(cmd);
            }
        }

        /// <summary>
        /// 读取
        /// </summary>
        /// <returns></returns>
        public byte[] Read()
        {
            lock (this)
            {
                if (FunctionTryConnect == null || FunctionTryConnect.Invoke())
                {
                    if (mStream.Length > 0)
                    {
                        var buffer = new byte[mStream.Length];
                        mStream.Read(buffer);
                        return buffer;
                    }
                }
                return new byte[0];
            }
        }

        /// <summary>
        /// 读取
        /// </summary>
        /// <param name="cmd">指令</param>
        /// <param name="expectedLength">期望长度</param>
        /// <returns></returns>
        public byte[] Read(byte[] cmd, long expectedLength)
        {
            if (cmd == null || cmd.Length == 0)
                return null;
            lock (this)
            {
                if (FunctionTryConnect == null || FunctionTryConnect.Invoke())
                {
                    if (mStream.Length > 0)
                    {
                        GetUnexpectedBuffer(cmd);
                    }
                    mStream.Write(cmd);
                    for (int i = 0; mStream.Length < expectedLength && i < this.TryLimit; i++)
                    {
                        Thread.Sleep(TryDelay);
                    }
                    if (mStream.Length < expectedLength)
                    {
                        GetUnexpectedBuffer(cmd);
                        return null;
                    }
                    var buffer = new byte[mStream.Length];
                    mStream.Read(buffer, 0, buffer.Length);
                    return buffer;
                }
                return new byte[0];
            }

        }

        /// <summary>
        /// 读取
        /// </summary>
        /// <param name="cmd">指令</param>
        /// <param name="delay">查询间隔</param>
        /// <returns></returns>
        public byte[] Read(byte[] cmd, int delay = 100)
        {
            if (cmd == null || cmd.Length == 0)
                return null;
            lock (this)
            {
                if (FunctionTryConnect == null || FunctionTryConnect.Invoke())
                {
                    if (mStream.Length > 0)
                    {
                        GetUnexpectedBuffer(cmd);
                    }
                    mStream.Write(cmd);
                    Thread.Sleep(delay);
                    long tempLength = -1;
                    for (int i = 0; mStream.Length != tempLength && i < this.TryLimit; i++)
                    {
                        tempLength = mStream.Length;
                        Thread.Sleep(TryDelay);
                    }
                    var buffer = new byte[mStream.Length];
                    mStream.Read(buffer, 0, buffer.Length);
                    return buffer;
                }
                return new byte[0];
            }
        }

        /// <summary>
        /// 读取
        /// </summary>
        /// <param name="cmd">指令</param>
        /// <param name="headerLength">返回消息头长度</param>
        /// <param name="headerReceived">处理消息头的过程</param>
        /// <returns></returns>
        public byte[] ReadWithHeader(byte[] cmd, long headerLength, HeaderReceived<byte[]> headerReceived)
        {
            if (cmd == null || cmd.Length == 0)
                return null;
            lock (this)
            {
                if (FunctionTryConnect == null || FunctionTryConnect.Invoke())
                {
                    if (mStream.Length > 0)
                    {
                        GetUnexpectedBuffer(cmd);
                    }
                    mStream.Write(cmd);
                    for (int i = 0; mStream.Length < headerLength && i < this.TryLimit; i++)
                    {
                        Thread.Sleep(TryDelay);
                    }
                    if (mStream.Length < headerLength)
                    {
                        GetUnexpectedBuffer(cmd);
                        return null;
                    }
                    List<byte> result = new List<byte>();
                    var buffer = new byte[headerLength];
                    mStream.Read(buffer, 0, buffer.Length);
                    result.AddRange(buffer);
                    var bodyLength = headerReceived(buffer);
                    buffer = new byte[bodyLength];
                    mStream.Read(buffer, 0, buffer.Length);
                    result.AddRange(buffer);
                    return result.ToArray();
                }
                return new byte[0];
            }
        }

        /// <summary>
        /// 写入
        /// </summary>
        /// <param name="cmd">指令</param>
        public void WriteString(string cmd)
        {
            this.Write(this.Encoding.GetBytes(cmd));
        }

        /// <summary>
        /// 读取
        /// </summary>
        /// <returns></returns>
        public string ReadString()
        {
            byte[] buffer = this.Read();
            return this.Encoding.GetString(buffer, 0, buffer.Length);
        }

        /// <summary>
        /// 读取
        /// </summary>
        /// <param name="cmd">指令</param>
        /// <param name="expectedLength">期望长度</param>
        /// <returns></returns>
        public string ReadString(string cmd, long expectedLength)
        {
            if (string.IsNullOrEmpty(cmd))
                return null;
            var byteCmd = this.Encoding.GetBytes(cmd);
            var byteRespon = Read(byteCmd, expectedLength);
            return this.Encoding.GetString(byteRespon, 0, byteRespon.Length);
        }

        /// <summary>
        /// 读取
        /// </summary>
        /// <param name="cmd">指令</param>
        /// <param name="delay">查询间隔</param>
        /// <returns></returns>
        public string ReadString(string cmd, int delay = 100)
        {
            if (string.IsNullOrEmpty(cmd))
                return null;
            var byteCmd = this.Encoding.GetBytes(cmd);
            var byteRespon = Read(byteCmd, delay);
            return this.Encoding.GetString(byteRespon, 0, byteRespon.Length);
        }

        /// <summary>
        /// 读取
        /// </summary>
        /// <param name="cmd">指令</param>
        /// <param name="headerLength">返回消息头长度</param>
        /// <param name="headerReceived">处理消息头的过程</param>
        /// <returns></returns>
        public string ReadStringWithHeader(string cmd, long headerLength, HeaderReceived<string> headerReceived)
        {
            if (string.IsNullOrEmpty(cmd))
                return null;
            var byteCmd = this.Encoding.GetBytes(cmd);
            if (byteCmd == null || byteCmd.Length == 0)
                return null;
            lock (this)
            {
                if (FunctionTryConnect == null || FunctionTryConnect.Invoke())
                {
                    if (mStream.Length > 0)
                    {
                        GetUnexpectedBuffer(byteCmd);
                    }
                    mStream.Write(byteCmd);
                    for (int i = 0; mStream.Length < headerLength && i < this.TryLimit; i++)
                    {
                        Thread.Sleep(TryDelay);
                    }
                    if (mStream.Length < headerLength)
                    {
                        GetUnexpectedBuffer(byteCmd);
                        return null;
                    }
                    List<byte> result = new List<byte>();
                    var buffer = new byte[headerLength];
                    mStream.Read(buffer, 0, buffer.Length);
                    result.AddRange(buffer);
                    var bodyLength = headerReceived(this.Encoding.GetString(buffer));
                    buffer = new byte[bodyLength];
                    mStream.Read(buffer, 0, buffer.Length);
                    result.AddRange(buffer);
                    return this.Encoding.GetString(result.ToArray());
                }
                return string.Empty;
            }
        }

        /// <summary>
        /// 获取意外粘包
        /// </summary>
        /// <param name="cmd">指令</param>
        private void GetUnexpectedBuffer(byte[] cmd)
        {
            var tempLength = mStream.Length;
            Thread.Sleep(TryDelay);
            while (mStream.Length != tempLength)
            {
                tempLength = mStream.Length;
                Thread.Sleep(TryDelay);
            }
            var unexpectedBuffer = new byte[mStream.Length];
            mStream.Read(unexpectedBuffer, 0, unexpectedBuffer.Length);
            UnexpectedBufferReceivedEventHandler?.Invoke(this, cmd, unexpectedBuffer);
        }
    }
}
